#include <QtGui>
#include <fstream>
#include <iostream>
#include <assert.h>
#include <QSettings>
#include <QFileDialog>
#include <QMessageBox>
#include <QFileInfo>

#include "include/treeitem.h"
#include "include/treemodel.h"
#include "include/Common.h"
#include "include/Deck.h"
#include "include/cardsreader.h"
#include "include/itreeitemfactory.h"

using namespace std;


namespace
{

	const QString LastLoadedFileKey = "LastLoadedFile";
	ITreeItemFactory* const  defaultFacotry = new SiblingItemFactory; //const pointer on non-constant object
	const char* ItemIsEmptyText = "<empty>";
	const char* ItemContainsOnlyImageText = "<image>";

	bool itemIsPaused(const TreeItem* item)
	{
		if (item->isPaused())
			return true;

		if (item->parent() != 0)
			return itemIsPaused(item->parent());

		return false;
	}

	QString getDir(const QString& path)
	{
		if (path.isEmpty())
			return path;

		QFileInfo info(path);
		return info.canonicalPath();
	}

}



struct TreeModel::ModelResetHelper
{
		ModelResetHelper(TreeModel* model)
			: _model(model)
		{
		//	_model->beginResetModel();
		}

		~ModelResetHelper()
		{
		//	_model->endResetModel();
		}

	private:

		TreeModel* _model;
};





TreeModel::TreeModel(QObject *parent)
	: QAbstractItemModel(parent)
	, _unsavedChanges(false)
	, m_pTreeItemFactory(0)
{
	QVector<QVariant> rootData;

	//foreach (QString header, headers)
	//rootData << header;

	m_pKnowledgeBase = new KnowledgeBase();

	//QFile deckfile(getHome() + "/Dropbox/data_together.db");

	m_pRootItem = new TreeItem(0); //rootItem does not have parents

	m_pRootItem->setQuestionText(tr("Question"));
	m_pRootItem->setAnswerText(tr("Answer"));

	m_pRootItem->setKnowledgeBase(m_pKnowledgeBase);

	loadLastFile();
}


TreeModel::~TreeModel()
{
	delete m_pRootItem;
	delete m_pKnowledgeBase;
}

int TreeModel::columnCount(const QModelIndex & /* parent */) const
{
	return m_pRootItem->columnCount();
}

QVariant TreeModel::data(const QModelIndex &index, int role) const
{
	if (!index.isValid())
		return QVariant();

	TreeItem *item = getItem(index);
	Qt::ItemDataRole r = (Qt::ItemDataRole)role;

	switch (r)
	{

	case Qt::ForegroundRole:
	{
		if (itemIsPaused(item))
		{
			return QVariant(QColor(Qt::gray));
		}
		if (item->isCard())
		{
			return QVariant(QColor(item->getCard()->isDue() ? Qt::darkRed : Qt::darkGreen));
		}
		else if (item->childCount() == 0)
			return QVariant(QColor(Qt::black));
		else
		{
			if (item->hasCards())
				return QVariant(QColor(item->isDue() ? Qt::darkRed : Qt::darkGreen));
			else
				return QVariant(QColor(Qt::black));
		}
		return QVariant(QColor(Qt::black));
	}
	case Qt::DisplayRole:
		return displayRoleData(item, (TreeItem::DataColumn)index.column());

	case Qt::EditRole:
		return editRoleData(item, (TreeItem::DataColumn)index.column());

	default:
		return QVariant();

	}
}


QVariant TreeModel::displayRoleData(TreeItem* item, TreeItem::DataColumn col) const
{
	if (item != 0)
	{
		switch (col)
		{

		case TreeItem::QuestionColumn:
		{
			QString text = item->questionText();
			if (text.isEmpty())
				return item->hasImage(col) ? tr(ItemContainsOnlyImageText) : tr(ItemIsEmptyText);
			return text;
		}

		case TreeItem::AnswerColumn:
			return item->answerText();

		default:
			return QVariant();

		}
	}

	return QVariant();
}


QVariant TreeModel::editRoleData(TreeItem* item, TreeItem::DataColumn col) const
{
	return item != 0 ? item->data(col) : QVariant();
}



Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
	if (!index.isValid())
		return 0;

	return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}


TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
	if (index.isValid())
	{
		TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
		if (item) return item;
	}
	return m_pRootItem;
}

unsigned int TreeModel::getNumCards(const QModelIndex &index) const
{
	if (index.isValid())
	{
		TreeItem* ti = getItem(index);
		return ti->getNumCards();
	}
	return 0;
}

QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
							   int role) const
{
	if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
		return m_pRootItem->data(section);

	return QVariant();
}


QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
	if (parent.isValid() && parent.column() != 0)
		return QModelIndex();

	TreeItem *parentItem = getItem(parent);

	TreeItem *childItem = parentItem->child(row);
	if (childItem)
		return createIndex(row, column, childItem);
	else
		return QModelIndex();
}

bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent)
{
	TreeItem *parentItem = getItem(parent);
	bool success;
	beginInsertRows(parent, position, position + rows - 1);
	success = parentItem->insertChildren(position, rows, currentOrDefaultTreeItemFactory());
	endInsertRows();

	if (success)
	{
		emit dataChanged(this->index(parent.row(), 0, parent.parent()), index(parent.row(), TreeItem::ColumnsCount-1, parent.parent()));
		markUnsaved();
	}

	return success;
}


QModelIndex TreeModel::parent(const QModelIndex &index) const
{
	if (!index.isValid())
		return QModelIndex();

	TreeItem *childItem = getItem(index);
	TreeItem *parentItem = childItem->parent();

	if (parentItem == m_pRootItem)
		return QModelIndex();

	return createIndex(parentItem->childNumber(), 0, parentItem);
}

bool TreeModel::removeRows(int position, int rows, const QModelIndex &parent)
{
	TreeItem *parentItem = getItem(parent);
	bool success = true;

	beginRemoveRows(parent, position, position + rows - 1);
	success = parentItem->removeChildren(position, rows);
	endRemoveRows();

	if (success)
	{
		emit dataChanged(this->index(parent.row(), 0, parent.parent()), index(parent.row(), TreeItem::ColumnsCount-1, parent.parent()));
		markUnsaved();
	}

	return success;
}


int TreeModel::rowCount(const QModelIndex &parent) const
{
	TreeItem *parentItem = getItem(parent);

	return parentItem->childCount();
}


bool TreeModel::setData(const QModelIndex &index, const QVariant &value,
						int role)
{
	if (role == Qt::EditRole)
	{
		TreeItem *item = getItem(index);
		bool result = item->setData(index.column(), value);

		if (result)
		{
			markUnsaved();
			emit dataChanged(index, index);
			emit treeItemChanged(*item);
		}

		return result;
	}


	return false;
}


bool TreeModel::setHeaderData(	int section, Qt::Orientation orientation,
								const QVariant &value, int role)
{
	if (role != Qt::EditRole || orientation != Qt::Horizontal)
		return false;

	bool result = m_pRootItem->setData(section, value);

	if (result)
		emit headerDataChanged(orientation, section, section);

	return result;
}


bool TreeModel::cut( int position, int rows, const QModelIndex &parent)
{
	TreeItem *parentItem = getItem(parent);
	bool success = true;

	beginRemoveRows(parent, position, position + rows - 1);
	success = parentItem->cutChildren(position, rows);
	endRemoveRows();

	if (success)
		markUnsaved();

	return success;
}

bool TreeModel::paste( int position, int rows, const QModelIndex &parent)
{
	TreeItem *parentItem = getItem(parent);
	bool success = true;

	beginInsertRows(parent, position, position + rows - 1);
	success = parentItem->paste(position, rows);
	endInsertRows();

	if (success)
		markUnsaved();

	return success;
}


QModelIndex TreeModel::indexOf(TreeItem* pTi) const
{
	if (pTi == 0)
		return QModelIndex();
	return createIndex(pTi->childNumber(), 0, pTi);
}


void TreeModel::getDueCardsDepthFirst(const QModelIndex& index, CardInfo& info) const
{
	TreeItem* pTi = 0;
	bool isdue = getItem(index)->isDue();
	if (isdue)
	{
		getItem(index)->getDueCardsDepthFirst(pTi, info, QList<QString>());
		if (info.m_card != 0 && pTi != 0 && pTi->parent() != 0)
		{
			//info.m_modelIndex = createIndex(pTi->childNumber(), 0, (void*)pTi);
			info.m_treeItem = pTi;
		}
	}
	else
		info = CardInfo();
}


bool	 TreeModel::hasDueCards() const
{
	CardInfo info;
	getDueCardsDepthFirst(QModelIndex(), info);
	return info.m_card != 0 && info.m_card->isNull() == false;
}

void	 TreeModel::invertItem(const QModelIndex& index)
{
	TreeItem* pItem = getItem(index);
	if (pItem)
	{
		pItem->invert();
		markUnsaved();

		emit treeItemChanged(*pItem);
		emit dataChanged(index, index);
	}
}

template <typename T>
QList<T> reversed( const QList<T> & in ) {
	QList<T> result;
	//result.reserve( in.size() ); // reserve is new in Qt 4.7
	std::reverse_copy( in.begin(), in.end(), std::back_inserter( result ) );

	return result;
}


QList<QString> TreeModel::getLineage(const QModelIndex& index) const
{
	QList<QString> lineage;
	TreeItem* ti = getItem(index);
	if (ti)
	{
		TreeItem* parent = ti->parent();
		if (parent)
			parent->getLineage(lineage);
	}
	return reversed(lineage);
}

CardInfo TreeModel::getCardInfo(const QModelIndex& index)
{
	CardInfo info;
	TreeItem* ti = getItem(index);
	if (ti)
	{
		info.m_card = ti->getCard();
		//info.m_modelIndex = index;
		info.m_treeItem = ti;
		info.m_lineage = getLineage(index);
	}
	return info;
}

int TreeModel::getDueDate(const QModelIndex& index)
{
	return getItem(index)->getDueDate();
}

void TreeModel::convertSelectionToCards(const QModelIndex& index, bool b)
{
	TreeItem *item = getItem(index);
	item->setIsCard(b);
	markUnsaved();
	emit dataChanged(createIndex(index.row(), 0, item), createIndex(index.row(), 1, item));
}

CardInfo	 TreeModel::getNextDueCard() const
{
//	m_currentReviewCard = m_currentReviewCard->getNextDueCard();
//	return m_currentReviewCard;
	assert(false);
}

void TreeModel::pauseBranch(const QModelIndex& index, bool b)
{
	setBranchPaused(getItem(index), b);
}

void TreeModel::setBranchPaused(TreeItem* item, bool paused)
{
	if (item == 0 || item == m_pRootItem || item->isPaused() == paused)  //issue #11, whole tree may not be paused/unpaused
		return;


	item->setIsPaused(paused);

	markUnsaved();

	QModelIndex idx = indexOf(item);
	emit dataChanged(idx, index(idx.row(), TreeItem::ColumnsCount -1, idx.parent()));

	if (item->hasChildren())
	{
		emit dataChanged(index(0, 0, idx), index(item->childCount(), TreeItem::ColumnsCount - 1, idx));
	}
}


void	 TreeModel::convertCardToTwig(const QModelIndex& index)
{
	TreeItem *item = getItem(index);
	item->setIsCard(false);
	markUnsaved();
	emit dataChanged(index, index);
}


Card* TreeModel::getCard(const QModelIndex& index)
{
	TreeItem* item = getItem(index);

	QString msg = item != 0 ? item->data(0).toString() : "no item found";
	QMessageBox::information(0, "Card", msg);

	return item != 0 ? item->getCard() : 0;
}


TreeItem* TreeModel::getItemByUuid(const QUuid& uuid) const
{
	return m_pKnowledgeBase->getTreeItem(uuid);
}

void TreeModel::notifyItemUpdate(TreeItem* item)
{
	if (item == 0)
		return;

	int row = item->parent() != 0 ? item->parent()->indexOfChild(item) : 0;
	QModelIndex lt = createIndex(row, TreeItem::QuestionColumn, item); //use std::min
	QModelIndex rb = createIndex(row, TreeItem::AnswerColumn, item); //use std::max

	markUnsaved();

	emit dataChanged(lt, rb);
	emit treeItemChanged(*item);
}



void TreeModel::loadLastFile()
{
	//this also covers the change of m_currrentReviewCard below
	ModelResetHelper(this); //Pattern: resource acquisition is initialization

	QString lastFilePath = lastLoadedFile();

	m_currentReviewCard = m_pRootItem; //model will be updated

	if (lastFilePath.isEmpty())
	{
		QString defaultPath = getHome() + "/Dropbox/data_together.db";
		lastFilePath = defaultPath;
		setLastLoadedFile(defaultPath);

		if (!QFile::exists(defaultPath))
		{
			saveToFile(defaultPath);
			return;
		}
	}

	//only default file can be created on disk automatically
	//if last file is not empty and it does not exist (e.g. was removed manually) we do not create new files on disk;

	if (QFile::exists(lastFilePath))
		loadFromFile(lastFilePath);
}

bool TreeModel::saveTreeAs()
{
	QString initialPath = !_currentFileName.isEmpty() ? _currentFileName : getDir(lastLoadedFile());
	QString selectedPath = QFileDialog::getSaveFileName(0, tr("Save As..."), initialPath, tr("Mu Files (*.db)"));
	if (selectedPath.isEmpty())
		return false;

		_currentFileName = selectedPath;
	saveToFile(selectedPath);
	setLastLoadedFile(selectedPath);
	resetUnsavedFlag();

	return true;
}

bool TreeModel::saveChanges()
{
	if (_currentFileName.isEmpty())
		return saveTreeAs();

	saveToFile(_currentFileName);
	resetUnsavedFlag();

	return true;
}

void TreeModel::createTree()
{
	if (!askModelDropConfirmation())
		return;

	ModelResetHelper(this); //Pattern: resource acquisition is initialization
	clearTree();
	_currentFileName = "";
	resetUnsavedFlag();

	emit newTreeCreated();
}

void TreeModel::loadNewFile()
{
	if (!askModelDropConfirmation())
		return;

	QString selectedPath = QFileDialog::getOpenFileName(0, tr("Open..."), lastLoadedFile(), tr("Mu Files (*.db)"));
	if (selectedPath.isEmpty())
		return;

	ModelResetHelper(this); //Pattern: resource acquisition is initialization
	clearTree();

	loadFromFile(selectedPath);
	setLastLoadedFile(selectedPath);
	resetUnsavedFlag();
}

bool TreeModel::confirmApplicationExit()
{
	if (!askModelDropConfirmation())
		return false;

	clearTree();
	resetUnsavedFlag();

	return true;
}


void TreeModel::clearTree()
{
	//clear the members in order opposite to their initialization
	m_currentReviewCard = m_pRootItem;
	m_pRootItem->removeChildren(0, m_pRootItem->childCount()); //first step
	m_pKnowledgeBase->clear(); //second step
}

void TreeModel::loadFromFile(const QString path)
{
	QFile deckfile(path);

	deckfile.open(QIODevice::ReadOnly);
	QDataStream in(&deckfile);

	CardsReader r;
	r.readFrom(in, *m_pRootItem);

	m_pRootItem->setQuestionText(tr("Question"));
	m_pRootItem->setAnswerText(tr("Answer"));

	_currentFileName = path;
}


void TreeModel::saveToFile(const QString path)
{
	QFile file(path);
	file.open(QIODevice::WriteOnly);
	QDataStream out(&file);
	const TreeItem& ti = *m_pRootItem;

	out << CardsReader::MagicNumber;
	out << CardsReader::CurrentReaderVersion;

	out << *m_pKnowledgeBase->deck();
	out << ti;
	file.close();
}


bool TreeModel::askModelDropConfirmation()
{
	if (!hasUnsavedChanges()) //confirmation is not needed, nothing to save
		return true;

	QMessageBox m(QMessageBox::Question,
					tr("Confirmation Request"),
					tr("Knowledge tree has been changed. Would you like to save the changes?"),
					QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);

	QSize mSize = m.sizeHint();

	m.show(); // not recommended

	QRect screenRect = QDesktopWidget().screen()->rect();
	m.move( QPoint( screenRect.width()/2 - mSize.width()/2,
					screenRect.height()/2 - mSize.height()/2 ) );

	QMessageBox::StandardButton result = (QMessageBox::StandardButton)m.exec();

	if (result == QMessageBox::Cancel)
		return false;

	if (result == QMessageBox::Yes) // "SaveAs" can return false if user presses Cancel, in this case we consider this as cancellation of the enclosing operation
		return saveChanges();

	return true;
}



QString TreeModel::lastLoadedFile() const
{
	QSettings settings;
	QVariant v = settings.value(LastLoadedFileKey);

	return v.isValid() ? v.toString() : "";
}

void TreeModel::setLastLoadedFile(const QString& value)
{
	QSettings settings;
	settings.setValue(LastLoadedFileKey, value);
}

bool TreeModel::hasUnsavedChanges() const
{
	return _unsavedChanges;
}

void TreeModel::markUnsaved()
{
	_unsavedChanges = true;
}

void TreeModel::resetUnsavedFlag()
{
	_unsavedChanges = false;
}



ITreeItemFactory* TreeModel::treeItemFactory() const
{
	return m_pTreeItemFactory;
}

void TreeModel::setTreeItemFactory(ITreeItemFactory* factory)
{
	m_pTreeItemFactory = factory;
}


ITreeItemFactory* TreeModel::currentOrDefaultTreeItemFactory() const
{
	return m_pTreeItemFactory != 0 ? m_pTreeItemFactory : defaultFacotry;
}


//TreeItem* TreeModel::createTreeItem(const QVector<QVariant> &data, TreeItem *parent) const
//{
//	return currentOrDefaultTreeItemFactory()->createTreeItem(data, parent);
//}

//TreeItem* TreeModel::createTreeItem(TreeItem *parent) const
//{
//	return currentOrDefaultTreeItemFactory()->createTreeItem(parent);
//}


//void TreeModel::createAndInsertNode(TreeItem* parentItem, int insertionPosition)
//{
//	QModelIndex index = indexOf(parentItem);
//	beginInsertRows(index, insertionPosition, insertionPosition);

//	success = parentItem->insertChildren(position, inser, m_pRootItem->columnCount());

//	if (success)
//	{
//		this->m_pKnowledgeBase->setTreeItem(parentItem->child());
//		markUnsaved();
//	}

//	endInsertRows();
//}
